/**
 * <p>文件名:		RsaUtils.java</p>
 * <p>版权:		CopyrightTag</p>
 * <p>公司:		CompanyTag</p>
 * @author		周华彬(zhouhuabin@chinatransinfo.com, zhou_hua_bin@163.com)
 */

package com.caits.lbs.framework.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.slf4j.Logger;

import com.caits.lbs.framework.log.CommonSlf4jLogFactory;

/**
 * <p>
 * RsaUtils
 * </p>
 * <p>
 * rsa非对称加密算法，默认采用1024位
 * </p>
 * @author 周华彬(zhouhuabin@chinatransinfo.com, zhou_hua_bin@163.com)
 * @version 0.0.0
 *          <table style="border:1px solid gray;">
 *          <tr>
 *          <th width="100px">版本号</th>
 *          <th width="100px">动作</th>
 *          <th width="100px">修改人</th>
 *          <th width="100px">修改时间</th>
 *          </tr>
 *          <!-- 以 Table 方式书写修改历史 -->
 *          <tr>
 *          <td>0.0.0</td>
 *          <td>创建类</td>
 *          <td>zhouhuabin</td>
 *          <td>2015年12月1日 下午2:47:57</td>
 *          </tr>
 *          <tr>
 *          <td>XXX</td>
 *          <td>XXX</td>
 *          <td>XXX</td>
 *          <td>XXX</td>
 *          </tr>
 *          </table>
 */
public class RsaUtils {
	/** 变量:TODO,类型:Logger */
	protected static Logger log = CommonSlf4jLogFactory.getLog();
	/** 指定加密算法为RSA */
	public static final String ALGORITHM = "RSA";
	/**
	 * String to hold name of the encryption padding.
	 */
	public static final String PADDING = "RSA/NONE/NoPadding";
	/**
	 * String to hold name of the security provider.
	 */
	public static final String PROVIDER = "BC";

	/** 变量:指定签名算法为SHA1WithRSA,类型:String */
	public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
	/** 指定key的密钥长度bit,就是128byte，最大能加密的明文长度就是128-11 */
	private static int KEYSIZE = 1024;

	/** RSA算法要求有一个可信任的随机数源 */
	private static SecureRandom sr = new SecureRandom();

	/** 变量:私钥,类型:int */
	private final static int KEYTYPE_PRIVATE = Cipher.PRIVATE_KEY;
	/** 变量:公钥,类型:int */
	private final static int KEYTYPE_PUBLIC = Cipher.PUBLIC_KEY;

	/** 变量:加密,类型:int */
	private final static int CRYPTTYPE_ENCRYPT = Cipher.ENCRYPT_MODE;
	/** 变量:解密,类型:int */
	private final static int CRYPTTYPE_DECRYPT = Cipher.DECRYPT_MODE;

	/**
	 * 生成密钥对
	 * 
	 * <pre>
	 * // 得到公钥
	 * Key publicKey = kp.getPublic();
	 * // 得到私钥
	 * Key privateKey = kp.getPrivate();
	 * </pre>
	 * @return KeyPair 秘钥公钥对
	 */
	public static KeyPair generateKeyPair() {
		try {
			/** 为RSA算法创建一个KeyPairGenerator对象 */
			KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);
			/** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
			kpg.initialize(KEYSIZE, sr);
			/** 生成密匙对 */
			KeyPair kp = kpg.generateKeyPair();
			return kp;
		} catch (Exception e) {
			log.error("生成rsa秘钥对错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥rsa加密 二进制
	 * @param publicKey 公钥对象数组
	 * @param sourceByte 待加密的数据
	 * @return byte[] 加密后的数据
	 * @throws Exception String
	 */
	public static byte[] encryptUsePublicKey(byte[] publicKey, byte[] sourceByte) throws Exception {
		try {
			RSAPublicKey key = (RSAPublicKey) generateKey(publicKey, KEYTYPE_PUBLIC);
			return encryptUsePublicKey(key, sourceByte);
		} catch (Exception e) {
			log.error("加密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥rsa加密 字符串，不建议加密大于1KB的数据
	 * @param publicKey 公钥字符串
	 * @param source 待加密的数据
	 * @param charSet 字符编码
	 * @return
	 * @throws Exception byte[]
	 */
	public static byte[] encryptUsePublicKey(String publicKey, String source, String charSet) throws Exception {
		try {
			RSAPublicKey key = (RSAPublicKey) generateKey(Base64Codec.decode(publicKey.toCharArray()), KEYTYPE_PUBLIC);
			return encryptUsePublicKey(key, source.getBytes(charSet));
		} catch (Exception e) {
			log.error("加密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥rsa加密 二进制，不建议加密大于1KB的数据
	 * @param publicKey 公钥对象
	 * @param sourceByte 待加密的数据
	 * @return byte[] 加密后的数据
	 * @throws Exception String
	 */
	public static byte[] encryptUsePublicKey(PublicKey publicKey, byte[] sourceByte) throws Exception {
		try {
			/** 得到Cipher对象来实现对源数据的RSA加密 */
			// Cipher.getInstance(PADDING, PROVIDER);
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			/** 执行加密操作 */
			return mergeDoFinalByte(sourceByte,cipher,CRYPTTYPE_ENCRYPT);
		} catch (Exception e) {
			log.error("加密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥加密数据，不建议加密大于1KB的数据
	 * @param privateKey 私钥二进制数组
	 * @param sourceByte 待签名数据
	 * @return 加密后的二进制
	 */
	public static byte[] encryptUsePrivateKey(byte[] privateKey, byte[] sourceByte) {
		try {
			PrivateKey priKey = (PrivateKey) generateKey(privateKey, KEYTYPE_PRIVATE);
			return encryptUsePrivateKey(priKey, sourceByte);
		} catch (Exception e) {
			log.error("RSA签名验证错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥加密字符串数据，不建议加密大于1KB的数据
	 * @param privateKey 私钥base64串
	 * @param content 待签名数据
	 * @param input_charset 编码格式
	 * @return 签名值
	 */
	public static String encryptUsePrivateKey(String privateKey, String content, String input_charset) {
		try {
			PrivateKey priKey = (PrivateKey) generateKey(Base64Codec.decode(privateKey.toCharArray()), KEYTYPE_PRIVATE);
			byte[] signed = encryptUsePrivateKey(priKey, content.getBytes(input_charset));
			return new String(Base64Codec.encode(signed));
		} catch (Exception e) {
			log.error("RSA签名验证错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥加密二进制，不建议加密大于1KB的数据
	 * @param priKey 私钥对象
	 * @param sourceByte 待加密的二进制
	 * @return byte[] 加密后的数组
	 */
	public static byte[] encryptUsePrivateKey(PrivateKey priKey, byte[] sourceByte) {
		try {
			/** 得到Cipher对象来实现对源数据的RSA加密 */
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.ENCRYPT_MODE, priKey);
			/** 执行加密操作 */
			return cipher.doFinal(sourceByte);
		} catch (Exception e) {
			log.error("RSA签名验证错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥rsa解密收到的数据
	 * @param privateKey 私钥对象
	 * @param rsaByte 待解密的数据
	 * @return byte[] 解密后的数据
	 * @throws Exception String
	 */
	public static byte[] decryptUsePrivateKey(byte[] privateKey, byte[] rsaByte) throws Exception {
		try {
			RSAPrivateKey key = (RSAPrivateKey) generateKey(privateKey, KEYTYPE_PRIVATE);
			/** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.DECRYPT_MODE, key);
			/** 执行解密操作 */
			return cipher.doFinal(rsaByte);
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥串解密 字符串
	 * @param privateKey 私钥串
	 * @param rsaStr 待解密的数据
	 * @param charSet 字符编码
	 * @return
	 * @throws Exception byte[]
	 */
	public static byte[] decryptUsePrivateKey(String privateKey, String rsaStr, String charSet) throws Exception {
		try {
			RSAPrivateKey key = (RSAPrivateKey) generateKey(Base64Codec.decode(privateKey.toCharArray()), KEYTYPE_PRIVATE);
			// 模
			String modulus = key.getModulus().toString();
			// 私钥指数
			String private_exponent = key.getPrivateExponent().toString();
//			log.info("构造出RSAPrivateKey对象的module={},exponent={}", modulus, private_exponent);
			return decryptUsePrivateKey(key, rsaStr.getBytes(charSet));
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用私钥rsa解密收到的数据
	 * @param privateKey 私钥对象
	 * @param rsaByte 待解密的数据
	 * @return byte[] 解密后的数据
	 * @throws Exception String
	 */
	public static byte[] decryptUsePrivateKey(PrivateKey privateKey, byte[] rsaByte) throws Exception {
		try {
			/** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			/** 执行解密操作 */
			return mergeDoFinalByte(rsaByte,cipher,CRYPTTYPE_DECRYPT);
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥串解密数据
	 * @param publicKey 公钥字符串
	 * @param rsaByte 待解密的数据
	 * @return byte[] 解密后的数据
	 * @throws Exception String
	 */
	public static byte[] decryptUsePublicKey(byte[] publicKey, byte[] source) throws Exception {
		try {
			PublicKey pubKey = (PublicKey) generateKey(publicKey, KEYTYPE_PUBLIC);
			/** 执行解密操作 */
			return decryptUsePublicKey(pubKey, source);
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥串解密数据
	 * @param publicKey 公钥字符串
	 * @param rsaByte 待解密的数据
	 * @return byte[] 解密后的数据
	 * @throws Exception String
	 */
	public static byte[] decryptUsePublicKey(String publicKey, String source, String charset) throws Exception {
		try {
			PublicKey pubKey = (PublicKey) generateKey(Base64Codec.decode(publicKey.toCharArray()), KEYTYPE_PUBLIC);
			return decryptUsePublicKey(pubKey, source.getBytes(charset));
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 用公钥对象解密数据
	 * @param publicKey 公钥对象
	 * @param rsaByte 待解密的数据
	 * @return byte[] 解密后的数据
	 * @throws Exception String
	 */
	public static byte[] decryptUsePublicKey(PublicKey publicKey, byte[] rsaByte) throws Exception {
		try {
			/** 得到Cipher对象对已用加密的数据进行公钥解密 */
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			/** 执行解密操作 */
			return cipher.doFinal(rsaByte);
		} catch (Exception e) {
			log.error("解密rsa数据错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 按类型生成钥匙对象
	 * @param keyData
	 * @param type
	 * @return Key
	 */
	public static Key generateKey(byte[] keyData, int type) {
		try {
			Key key = null;
			KeyFactory kf = KeyFactory.getInstance(ALGORITHM);
			switch (type) {
			case KEYTYPE_PRIVATE:
				PKCS8EncodedKeySpec encodedPrivateKey = new PKCS8EncodedKeySpec(keyData);
				key = kf.generatePrivate(encodedPrivateKey);
				return key;
			case KEYTYPE_PUBLIC:
				X509EncodedKeySpec encodedPublicKey = new X509EncodedKeySpec(keyData);
				key = kf.generatePublic(encodedPublicKey);
				return key;
			}
		} catch (Exception e) {
			log.error("生成rsa.key对象错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 使用模和指数生成RSA公钥 对象 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA /None/NoPadding】
	 * @param modulus 模
	 * @param exponent 指数
	 * @return
	 */
	public static RSAPublicKey getPublicKey(String modulus, String exponent) {
		try {
			BigInteger b1 = new BigInteger(modulus);
			BigInteger b2 = new BigInteger(exponent);
			KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
			RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
			return (RSAPublicKey) keyFactory.generatePublic(keySpec);
		} catch (Exception e) {
			log.error("生成RSAPublicKey对象错误,msg=" + e.getLocalizedMessage());
			return null;
		}
	}

	/**
	 * 使用模和指数生成RSA私钥 对象 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA /None/NoPadding】
	 * @param modulus 模
	 * @param exponent 指数
	 * @return
	 */
	public static RSAPrivateKey getPrivateKey(String modulus, String exponent) {
		try {
			BigInteger b1 = new BigInteger(modulus);
			BigInteger b2 = new BigInteger(exponent);
			KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
			RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);
			return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
		} catch (Exception e) {
			log.error("生成RSAPrivateKey对象错误,msg=" + e.getLocalizedMessage());
			return null;
		}
	}

	/**
	 * 合并处理密文的字节
	 * @param sourceByte
	 * @param cipher
	 * @param mode 1-加密,2-解密
	 * @return byte[]
	 */
	public static byte[] mergeDoFinalByte(byte[] sourceByte, Cipher cipher, int cryptType) {
		try {
			InputStream ins = new ByteArrayInputStream(sourceByte);
			ByteArrayOutputStream writer = new ByteArrayOutputStream(sourceByte.length / 2);
			int bufSize = (cryptType == CRYPTTYPE_ENCRYPT) ? 64 : 128;
			// rsa加密最多117,取2的倍数64,rsa解密的字节大小最多是128，将需要解密的内容，按128位拆开解密
			byte[] buf = new byte[bufSize];
			int bufl;
			while ((bufl = ins.read(buf)) != -1) {
				byte[] block = null;
				if (buf.length == bufl) {
					block = buf;
				} else {
					block = new byte[bufl];
					for (int i = 0; i < bufl; i++) {
						block[i] = buf[i];
					}
				}
				writer.write(cipher.doFinal(block));
			}
			return writer.toByteArray();
		} catch (Exception e) {
			log.error("合并处理密文的字节错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}

	/**
	 * 只能用私钥进行RSA签名
	 * @param priKey 私钥对象
	 * @param source 待签名字节
	 * @return 签名值
	 */
	public static byte[] signUsePrivateKey(PrivateKey priKey, byte[] source) {
		try {
			java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
			signature.initSign(priKey);
			signature.update(source);
			byte[] signed = signature.sign();
			return (signed);
		} catch (Exception e) {
			log.error("用私钥进行RSA签名错误,msg=" + e.getLocalizedMessage());
		}
		return null;
	}
	/** 
    * 用公钥 RSA签名检查
    * @param content 待签名数据 
    * @param sign 签名值 
    * @param publicKey 分配给开发商公钥 
    * @param encode 字符集编码 
    * @return 布尔值 
    */  
    public static boolean signVerifyUsePublicKey(PublicKey  pubKey, byte[] source, byte[] sign)  
    {  
    	try   
        {  
            java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);  
            signature.initVerify(pubKey);
            signature.update(source);  
            boolean bverify = signature.verify(sign);  
            return bverify;  
        }   
        catch (Exception e)   
        {  
        	log.error("RSA验签名检查错误,msg=" + e.getLocalizedMessage());
        }  
        return false;  
    }  
	/**
	 * FIXME
	 * @param args
	 * @throws Exception void
	 */
	public static void main(String[] args) throws Exception {
		// 一次生成，多次使用：可以保存
		KeyPair pair = generateKeyPair();
		String privateKey = new String(Base64Codec.encode(pair.getPrivate().getEncoded()));
		String publicKey = new String(Base64Codec.encode(pair.getPublic().getEncoded()));
		log.info("get private rsa base64str:" + privateKey);
		log.info("get public rsa base64str:" + publicKey);
		String source = "_input_charset=RSA&account_name=北京掌行通信息技术有限公司&batch_fee=2&batch_no=201512230000004&batch_num=1&detail_data=1^zhou_hua_bin@163.com^周华彬^2.00^停呗提现&email=yuqing@chinatransinfo.com&notify_url=http://rtti.3322.org/payservice/notify_url.jsp&partner=2088121216591811&pay_date=20151223&service=batch_trans_notify";// rsa要加密的字符串
		RSAPrivateKey privateK = (RSAPrivateKey) generateKey(Base64Codec.decode(privateKey.toCharArray()), 1);
		RSAPublicKey publicK = (RSAPublicKey) generateKey(Base64Codec.decode(publicKey.toCharArray()), 2);
		byte[] cryptosByte = encryptUsePublicKey(publicK, source.getBytes());// 生成的密文rsa
		log.info("encrypt data:" + new String(Base64Codec.encode(cryptosByte)));
		// 经测试，rsa加密后数据长度至少翻倍，私钥长度640，公钥长度160，modulus长度300，公钥exponent5位，私钥exponent300位
		byte[] target = decryptUsePrivateKey(privateK, cryptosByte);// 解密密文
		log.info("decrypt data:" + new String(target));

	}
}
